import jwt from 'jsonwebtoken';
import CryptoJS from 'crypto-js';
import fs from 'fs';
import path from 'path';
import moment from 'moment';
import rp from 'request-promise';
import axios from 'axios';
import mime from 'mime-types';
import shortid from 'shortid';
import net from 'net';
import { mysql } from 'src-ssr/db/connect';
import { Sequelize, Op } from 'sequelize';
import models, { sequelize } from 'src-ssr/db/sequelize';
import md5 from 'md5';
import stringSimilarity from 'string-similarity';

/**
 * 从token里取出租户id,用户id,用户名
 * @param {string} token jwt加密串
 * @return {object} { id, nickname } id: 用户id nickname: 用户名
*/
export const getLoginUser = (token) => {
  return new Promise((resolve, reject) => {
    jwt.verify(token, global.privateKey, (err, decoded) => {
      if (err) { // 伪造
        reject(err);
      } else {
        const { tid, id, nickname } = decoded;
        resolve({ tid, id, nickname });
      }
    });
  });
};

/**
 * 获取用户权限
 * @param {string} token jwt加密串
 * @param {string} menu 菜单名
 * @param {string} right 权限名
 * @param {string} tid 类型 0-系统用户 大于0-租户id
 * @return {boolean} 是否有权限 true-有 false-无
*/
export const getUserRights = (token, menu, right) => {
  return new Promise((resolve, reject) => {
    jwt.verify(token, global.privateKey, (err, decoded) => {
      if (err) { // 伪造
        reject(err);
      } else {
        const { id, tid } = decoded;
        models.m_users.findOne({
          attributes: ['rights'],
          where: { id, tenant_id: tid },
          raw: true
        }).then(row => {
          if (row) {
            const rights = row.rights;
            if (rights && rights[menu] !== undefined) {
              if (rights[menu][right] !== undefined) {
                resolve(rights[menu][right]);
              } else {
                console.log('not found right ' + right);
                resolve(null);
              }
            } else {
              console.log('not found menu ' + menu);
              resolve(null);
            }
          } else {
            console.log('not found user ' + id);
            resolve(null);
          }
        });
      }
    });
  });
}

/**
 * 验证用户权限
 * @param {string} user 当前用户
 * @param {string} menu 菜单名
 * @param {string, array} rights 权限
 * @return {object} {add: true, func: true, edit: false, ..., and: false}
*/
export const checkUserRight = async (user, menu, rights) => {
  let rightResult = {}
  try {
    const userInfo = await models.m_users.findOne({
      attributes: ['rights'],
      where: { id: user.id, tenant_id: user.tid },
      raw: true
    });

    let rightInfo = rights;
    if (typeof rights === 'string') rightInfo = rights.split(',');

    rightResult = rightInfo.reduce((a, b) => {
      a[b] = userInfo.rights[menu][b] === undefined ? userInfo.rights[menu][b] : !!userInfo.rights[menu][b];
      a.and = a.and && !!a[b];
      return a;
    }, { and: true });
  } catch (e) {
    console.error(e, user, menu, rights);
  }
  return rightResult;
}

/**
 * 根据用户id获取角色信息
 * @param {*} id 用户id
 * @param {string} tid 类型 0-系统用户 大于0-租户id
 * @returns {object}
 */
export const getUserRole = (id, tid) => {
  return new Promise((resolve, reject) => {
    models.m_users.findOne({
      attributes: [Sequelize.col('roles.id'), Sequelize.col('roles.role_name')],
      include: [{ model: Sequelize.model('roles'), attributes: [] }],
      where: { id, tenant_id: tid },
      raw: true
    }).then(row => {
      resolve(row)
    });
  });
};

/**
 * 更新登录次数
 * @param {string} id 用户id
 * @param {string} ip ip地址
 * @param {string} tid 类型 0-系统用户 大于0-租户id
*/
export const updateLogin = (id, ip, tid) => {
  // mysql.execute('update users set last_login=UNIX_TIMESTAMP(NOW()), login_count = login_count + 1 where id=?', [userid]);
  models.m_users.update({
    last_login_time: Date.now() / 1000,
    last_login_ip: ip || '',
    login_count: Sequelize.literal('login_count + 1')
  }, {
    where: { id, tenant_id: tid }
  });
};

/**
 * 加密
 * @param {string} str 待加密字符串
*/
export const encrypt = (str) => {
  // str为待加密字符串，key为密匙
  const keyHex = CryptoJS.enc.Utf8.parse(global.privateKey);
  const encrypted = CryptoJS.DES.encrypt(String(str), keyHex, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.ciphertext.toString();
};

/**
 * 解密
 * @param {string} ciphertext 待解密字符串
*/
export const decrypt = (ciphertext) => {
  // ciphertext为待解密字符串，key为密匙
  const keyHex = CryptoJS.enc.Utf8.parse(global.privateKey);
  const decrypted = CryptoJS.DES.decrypt(
    {
      ciphertext: CryptoJS.enc.Hex.parse(ciphertext)
    },
    keyHex,
    {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7
    }
  );
  return decrypted.toString(CryptoJS.enc.Utf8);
};

/**
 * 删除图片
 * @param {array} imgArr 待删除图片，格式为['uploads/202005/upload_c4558482431390567e8b5a1796c9d2c0.jpg', ...]
*/
export const deleteImage = (imgArr) => {
  const rootFolder = process.env.NODE_ENV === 'development' ? path.join(path.resolve(__dirname, '../..')) : path.join(path.resolve(__dirname, '..'));

  if (Object.prototype.toString.call(imgArr) === '[object Array]' && imgArr.length > 0) {
    imgArr.forEach(dir => {
      fs.unlink(path.join(rootFolder, dir), (err) => {
        err && console.log(err);
      });
    });
  }
};

/**
 * 删除文件
 * @param {array} imgArr 待删除图片，格式为['uploads/202005/upload_c4558482431390567e8b5a1796c9d2c0.jpg', ...]
*/
export const deleteFiles = (imgArr) => {
  const rootFolder = process.env.NODE_ENV === 'development' ? path.join(path.resolve(__dirname, '../..')) : path.join(path.resolve(__dirname, '..'));

  if (typeof imgArr === 'object' && imgArr.length > 0) {
    imgArr.forEach(dir => {
      fs.unlink(path.join(rootFolder, dir), (err) => {
        err && console.log(err);
      });
    });
  }
};

/**
 * 根据权限名称取有权限的人
 * @param {string} menu 菜单名称
 * @param {string} right 权限名
 * @param {boolean} value 权限值
 * @param {string} tid 类型 0-系统用户 大于0-租户id
 * @return {array} 有权限人的id
*/
export const getUserByRight = async (menu, right, value, tid) => {
  // const [rows] = await mysql.query('select id, rights from users');
  const rows = await models.m_users.findAll({
    attributes: ['id', 'rights'],
    WHERE: { tenant_id: tid },
    raw: true
  });
  const uids = [];
  for (const row of rows) {
    const rights = typeof row.rights === 'string' ? JSON.parse(row.rights) : row.rights;
    if (rights[menu] && rights[menu][right] === value) {
      uids.push(row.id);
    }
  }
  return uids;
};

/**
 * 获取配置参数
 * @param {string} key 参数名
 * @param {string} tid 类型 0-系统用户 大于0-租户id
*/
export const getSystemConfig = async (key, tid) => {
  const configs = await models.m_config.findAll({
    attributes: ['tenant_id', 'value'],
    where: {
      key,
      tenant_id: {
        [Op.in]: [0, tid]
      }
    },
    raw: true
  });

  let config = configs.find(v => v.tenant_id == tid); // 找出当前租户的配置
  if (!config) config = configs.find(v => v.tenant_id == 0); // 当前租户的若无配置则用公用配置

  return config ? config.value : '';
}

/**
 * @function 递归创建文件夹
 * @param {number} pos 深度 从0开始
 * @param {array} dirArray 文件夹数组
 * */
function mkdir (dirpath, pos = 0) {
  const dirArray = process.env.OS && process.env.OS.indexOf('Win') >= 0 ? dirpath.split('\\') : dirpath.split('/');

  if (pos >= dirArray.length) {
    return;
  }

  let currentDir = '';
  for (let i = 0; i <= pos; i++) {
    currentDir += '/' + dirArray[i];
  }

  // windows文件系统
  if (dirArray[0]) {
    currentDir = currentDir.slice(1);
  }

  if (!fs.existsSync(currentDir)) {
    fs.mkdirSync(currentDir);
  }

  mkdir(dirpath, pos + 1);// 递归建目录
}

/**
* 创建目录结构
*/
export const mkdirs = (dirpath) => {
  if (!fs.existsSync(dirpath)) {
    mkdir(dirpath);
  }
};

/**
 * @function 下载网络图片到指定目录
 * @param {string} url 网络下载链接
 * @param {string} filename 本地文件名
 * @return 返回本地url
 **/
export const downloadFromUrl = async (url, filename) => {
  if (!url) return '';
  // 下载存放路径：uploads/attachment/YYYYMM/
  const rootFolder = process.env.NODE_ENV === 'development' ? path.join(path.resolve(__dirname, '../..')) : path.join(path.resolve(__dirname, '..'));

  let attachmentFolder = '';
  let attachmentFile = '';
  let localFile = '';

  if (filename.toLowerCase().indexOf('uploads/') >= 0) {
    attachmentFolder = path.join(rootFolder, path.dirname(filename));
    attachmentFile = path.join(rootFolder, filename);
    localFile = filename;
  } else {
    attachmentFolder = path.join(rootFolder, `uploads/attachment/${moment().format('YYYYMM')}`);
    attachmentFile = path.join(attachmentFolder, filename);
    localFile = `uploads/attachment/${moment().format('YYYYMM')}/${filename}`;
  }

  if (!fs.existsSync(attachmentFolder)) {
    mkdir(attachmentFolder)
  }

  try {
    // request(url).pipe(fs.createWriteStream(attachmentFile));
    const res = await rp({ url, encoding: 'binary' });
    await fs.writeFileSync(attachmentFile, res, 'binary');
  } catch (e) {
    console.error(e, url, filename, localFile);
  }

  return localFile;
};

// 当前访问的域名
export const domainHost = (req) => {
  return `${req.protocol}://${req.headers.host}`;
  // return req.headers.origin || `${req.protocol}://${req.headers.host}`;
};

/**
 * 获取域名
 * 先从config表里取site的配置，若有则返回该域名
 * 若无，则从domain.txt表里取site的配置，若有则返回该域名
 * 若无，则返回当前访问的域名，并把域名写入domain.txt文件
 * @param {*} req req请求
 * @returns {string} 域名
 */
export const globalHost = async (req) => {
  let domainHost = `${req.protocol}://${req.headers.host}`; // 取当前访问的域名

  if (process.env.NODE_ENV === 'production') { // 生产环境，不用IP地址
    const config = await models.m_config.findOne({
      attributes: ['value'],
      where: {
        key: 'site'
      }
    });

    if (config && config.value && config.value.domain) { // 首选从数据库取数据
      domainHost = config.value.domain;
    } else if (fs.existsSync('../domain.txt')) { // 若无数据库设置则从文件中取
      domainHost = fs.readFileSync('../domain.txt');
    } else { // 再则若无数据库设置则使用当前域名并写入文件
      fs.writeFileSync('../domain.txt', domainHost);
    }
  }
  return domainHost;
};

/**
 * 功能：替换关键词
 * @param {string} srcStr 源字符串，其中包含需要被替换的关键词
 * @param {object} kv 关键键值对象
 * @param {string} left 被替换关键词的左边特殊字符
 * @param {string} right 被替换关键词的右边特殊字符
 */
export const replaceRealValue = (srcStr, kv, left = '{', right = '}') => {
  let newStr = srcStr;
  for (const key in kv) {
    newStr = newStr.replace(new RegExp(`${left}${key}${right}`, 'g'), kv[key]);
  }
  return newStr;
};

/**
 * 功能：逆地理编码，从高德api取
 * @param {string} longitude 经度
 * @param {string} latitude 纬度
*/
export const regeo = async (longitude, latitude) => {
  // const url = `https://restapi.amap.com/v3/geocode/regeo?location=${longitude},${latitude}&key=0ff1055803cf473cdd992079d04c4375&radius=100&extensions=all`;
  const body = await rp({
    uri: 'https://restapi.amap.com/v3/geocode/regeo',
    qs: {
      location: `${longitude},${latitude}`,
      key: '0ff1055803cf473cdd992079d04c4375',
      radius: '500',
      extensions: 'all'
    },
    json: true
  });
  // console.log(body);
  if (body.status == '1') {
    return `${body.regeocode.formatted_address}附近`;
  } else {
    return '';
  }
};

// 验证合法的IP地址
export const isValidIP = (ip) => {
  const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
  return reg.test(ip);
};

/**
 * 用sql建立树结构顺序的数据后建立树结构数据
 * @param {Object} root 根结点
 * @param {Array} rows 数据
 * @param {Object} sort 排序属性 可选，有值再进行排序
 *                  sort.name 用于排序的字段名
 *                  sort.type 字段类型 en - 英文(默认) cn - 中文 ,  num - 数值型
 * */
export const treeStructure = (root, rows, sort) => {
  const treeRoot = root ? { ...root } : { id: 0, parent_id: -1, label: '根结点', children: [], levels: 0 };
  // 递归找节点
  function findNode (rootNode, parentId) {
    if (rootNode.id == parentId) { // ID匹配
      return rootNode;
    } else {
      if (rootNode.children) { // 到子结点中找
        let node = null;
        for (const child of rootNode.children) {
          node = findNode(child, parentId);
          if (node) break;
        }
        return node;
      } else { // 无此id
        return null;
      }
    }
  }

  for (let i = 0; i < rows.length; i++) {
    const node = findNode(treeRoot, rows[i].parent_id);
    if (node) {
      if (!node.children) node.children = [];
      node.children.push(rows[i]);

      // 排序
      if (sort && node.children.length > 1) {
        const { name, type } = sort;

        node.children.sort((a, b) => {
          if (type === 'num') { // 比较数值
            return a[name] - b[name];
          } else if (type === 'cn') { // 比较中文
            return a[name].localeCompare(b[name], 'zh-CN');
          } else { // en 英文
            return a[name].localeCompare(b[name]);
          }
        });
      }
    }
  }
  return treeRoot;
}

/* 把树结构拉平 */
export const flatTree = (tree) => {
  const rows = [];

  function forTree (treeList) { // 遍历树函数
    for (const i in treeList) { // 循环遍历树
      rows.push({ ...treeList[i], children: [] });
      if (treeList[i].children && treeList[i].children.length > 0) {
        forTree(treeList[i].children)
      }
    }
  }

  forTree(tree);
  return rows;
};

/** 高效建树结构函数
 * @param {Object} root 根结点
 * @param {Array} rows 数据
 * @param {Object} sort 排序属性 可选，有值再进行排序
 *                  sort.name 用于排序的字段名
 *                  sort.type 字段类型 en - 英文(默认) cn - 中文 ,  num - 数值型
 * @param {String} idKey 关键id的属性名
 * */
export const buildTree = (root, rows, sort, idKey = 'id') => {
  let treeRoot;
  if (root) {
    treeRoot = { ...root, parent_id: -1, children: [], levels: 0 };
    treeRoot[idKey] = 0;
  } else {
    treeRoot = { parent_id: -1, label: '根结点', children: [], levels: 0 };
    treeRoot[idKey] = 0
  }
  const data = [...rows, treeRoot];

  // 建立 ID-数组索引映射关系
  const idMapping = data.reduce((acc, el, i) => {
    acc[el[idKey]] = i;
    return acc;
  }, {});
  // console.log(idMapping);

  // 构造树形结构
  data.forEach(el => {
    // 判断根节点
    if (el.parent_id === -1) {
      return;
    }
    // 用映射表找到父元素
    const parentEl = data[idMapping[el.parent_id]];
    // console.log(el[idKey], el.parent_id, idMapping[el.parent_id]);
    // 把当前元素添加到父元素的`children`数组中
    if (parentEl) {
      try {
        parentEl.children = [...(parentEl.children || []), el];
      } catch (e) {
        console.error(parentEl, data, idMapping, el.parent_id, e);
      }
    }
  });

  // 节点排序，赋层级
  (function forTree (treeList, levels) {
    // 排序
    if (sort && treeList.length > 1) {
      const { name, type } = sort;

      treeList.sort((a, b) => {
        if (type === 'num') { // 比较数值
          return a[name] - b[name];
        } else if (type === 'cn') { // 比较中文
          return a[name].localeCompare(b[name], 'zh-CN');
        } else {
          return a[name].localeCompare(b[name]);
        }
      });
    }

    for (const i in treeList) { // 循环遍历树
      treeList[i].levels = levels;

      if (treeList[i].children && treeList[i].children.length > 0) {
        forTree(treeList[i].children, levels + 1);
      }
    }
  })([treeRoot], 0);

  return [treeRoot];
};

// 附件根目录
export const attachRootPath = () => {
  let rootFolder = '';// 根据目录
  if (process.env.NODE_ENV === 'development') { // 开发环境
    rootFolder = path.resolve();
  } else { // 生产环境
    rootFolder = path.join(path.resolve(), '..');
  }

  return rootFolder;
};

// 判断是否为数值
export const isNumber = (val) => {
  const regPos = /^\d+(\.\d+)?$/; // 非负浮点数
  const regNeg = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; // 负浮点数
  if (regPos.test(val) || regNeg.test(val)) {
    return true;
  } else {
    return false;
  }
};

// 网络图片转base64
export const urlImgToBase64 = async (url) => {
  const res = await axios.get(url, { responseType: 'arraybuffer' });
  // eslint-disable-next-line new-cap
  const data = new Buffer.from(res.data, 'binary').toString('base64');
  return 'data:' + mime.lookup(url) + ';base64,' + data;
};

// 本地图片转base64
export const localImgToBase64 = (filepath) => {
  const rootFolder = process.env.NODE_ENV === 'development' ? path.join(path.resolve(__dirname, '../..')) : path.join(path.resolve(__dirname, '..'));
  const file = path.join(rootFolder, filepath);
  let data = '';
  if (fs.existsSync(file)) {
    data = fs.readFileSync(file);
    // eslint-disable-next-line new-cap
    data = new Buffer.from(data, 'binary').toString('base64');
  }

  return data && 'data:' + mime.lookup(file) + ';base64,' + data;
};

/**
 * 把base64转成图片
 * @param {string} base64 纯编码
 * @param {string} subdir 附件目录
 */
export const base64ToFile = (base64, subdir = '', filename) => {
  const rootFolder = process.env.NODE_ENV === 'development' ? path.join(path.resolve(__dirname, '../..')) : path.join(path.resolve(__dirname, '..'));
  const dir = path.join(rootFolder, 'uploads', subdir);
  mkdir(dir); // 创建目录

  fs.writeFileSync(path.join(dir, filename), Buffer.from(base64, 'base64'));

  return path.join('/uploads', subdir, filename);
};

/**
 * 十进制转26进制，即转成excel的列号
 * @param {Number} num 10进制数据
 * @returns {String} 列号
 */
export const convertDSTo26BS = (num) => {
  let code = '';
  const reg = /^\d+$/g;
  if (!reg.test(num)) {
    return code;
  }
  while (num > 0) {
    let m = num % 26
    if (m == 0) {
      m = 26;
    }
    code = String.fromCharCode(64 + parseInt(m)) + code;
    num = (num - m) / 26;
  }
  return code;
};

/**
 * 26进制转十进制，即把excel的列号转成数字
 * @param {String} code 列号
 * @returns 数字
 */
export const convert26BSToDS = (code) => {
  let num = -1;
  const reg = /^[A-Z]+$/g;
  if (!reg.test(code)) {
    return num;
  }
  num = 0;
  for (let i = code.length - 1, j = 1; i >= 0; i--, j *= 26) {
    num += (code[i].charCodeAt() - 64) * j;
  }
  return num;
};

/**
 * @function 把字符串写入文件
 * @param {string} buffer 字符串
 * @return 返回文件名
 **/
export const writeBufferToFile = (buffer) => {
  const rootFolder = process.env.NODE_ENV === 'development' ? path.join(path.resolve(__dirname, '../..')) : path.join(path.resolve(__dirname, '..'));
  const tempFolder = path.join(rootFolder, 'uploads/temporary');
  if (!fs.existsSync(tempFolder)) {
    mkdir(tempFolder)
  }
  const localFile = `${shortid.generate()}`;
  const dir = path.join(tempFolder, localFile);

  fs.writeFileSync(dir, typeof buffer === 'object' ? JSON.stringify(buffer) : buffer);
  return localFile;
};

/**
 * @function 从文件读取字符串
 * @param {string} file 文件名
 * @return 返回文件内容
 **/
export const readFromFile = (file, del = false) => {
  const rootFolder = process.env.NODE_ENV === 'development' ? path.join(path.resolve(__dirname, '../..')) : path.join(path.resolve(__dirname, '..'));
  const tempFolder = path.join(rootFolder, 'uploads/temporary');
  const dir = path.join(tempFolder, file);
  let buffer = ''
  if (fs.existsSync(dir)) {
    buffer = fs.readFileSync(dir);
  }
  if (del) {
    fs.unlinkSync(dir);
  }
  return buffer;
};

/**
 * 把数字金额转成中文
 * @param {*} money 数字金额
 * @returns 中文金额
 */
export const changeNumMoneyToChinese = (money) => {
  const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; // 汉字的数字
  const cnIntRadice = ['', '拾', '佰', '仟']; // 基本单位
  const cnIntUnits = ['', '万', '亿', '兆']; // 对应整数部分扩展单位
  const cnDecUnits = ['角', '分', '毫', '厘']; // 对应小数部分单位
  const cnInteger = '整'; // 整数金额时后面跟的字符
  const cnIntLast = '元'; // 整型完以后的单位
  // eslint-disable-next-line no-loss-of-precision
  const maxNum = 999999999999999.9999; // 最大处理的数字
  let IntegerNum; // 金额整数部分
  let DecimalNum; // 金额小数部分
  let ChineseStr = ''; // 输出的中文金额字符串
  let parts; // 分离金额后用的数组，预定义
  let Symbol = ''; // 正负值标记
  if (money == '') {
    return '';
  }

  money = parseFloat(money);
  if (money >= maxNum) {
    alert('超出最大处理数字');
    return '';
  }
  if (money == 0) {
    ChineseStr = cnNums[0] + cnIntLast + cnInteger;
    return ChineseStr;
  }
  if (money < 0) {
    money = -money;
    Symbol = '负 ';
  }
  money = money.toString(); // 转换为字符串
  if (money.indexOf('.') == -1) {
    IntegerNum = money;
    DecimalNum = '';
  } else {
    parts = money.split('.');
    IntegerNum = parts[0];
    DecimalNum = parts[1].substr(0, 4);
  }
  if (parseInt(IntegerNum, 10) > 0) { // 获取整型部分转换
    let zeroCount = 0;
    const IntLen = IntegerNum.length;
    for (let i = 0; i < IntLen; i++) {
      const n = IntegerNum.substr(i, 1);
      const p = IntLen - i - 1;
      const q = p / 4;
      const m = p % 4;
      if (n == '0') {
        zeroCount++;
      } else {
        if (zeroCount > 0) {
          ChineseStr += cnNums[0];
        }
        zeroCount = 0; // 归零
        ChineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
      }
      if (m == 0 && zeroCount < 4) {
        ChineseStr += cnIntUnits[q];
      }
    }
    ChineseStr += cnIntLast;
    // 整型部分处理完毕
  }
  if (DecimalNum != '') { // 小数部分
    const decLen = DecimalNum.length;
    for (let i = 0; i < decLen; i++) {
      const n = DecimalNum.substr(i, 1);
      if (n != '0') {
        ChineseStr += cnNums[Number(n)] + cnDecUnits[i];
      }
    }
  }
  if (ChineseStr == '') {
    ChineseStr += cnNums[0] + cnIntLast + cnInteger;
  } else if (DecimalNum == '') {
    ChineseStr += cnInteger;
  }
  ChineseStr = Symbol + ChineseStr;

  return ChineseStr;
}

// 判断是否为JSON
function _isJSON (str) {
  if (typeof str == 'string') {
    try {
      const obj = JSON.parse(str);
      if (typeof obj == 'object' && obj) {
        return true;
      } else {
        return false;
      }
    } catch (e) {
      // console.log('error：' + str + '!!!' + e);
      return false;
    }
  }
}

/**
 * 把所有内容都parse成json，包括值里的stringify
 * @param {array, object} allRows 待处理的行或对象
 */
export const parseAllJSON = (allRows) => {
  const type = Object.prototype.toString.call(allRows);
  if (['object Object]', '[object Array]'].includes(type)) {
    const parseRows = type === '[object Object]' ? [allRows] : allRows;
    (function parseAllJson (rows) {
      for (const row of rows) {
        if (Object.prototype.toString.call(row) === '[object Object]') {
          for (const key in row) {
            if (typeof row[key] === 'string' && _isJSON(row[key])) {
              row[key] = JSON.parse(row[key]);
            }

            if (Object.prototype.toString.call(row[key]) === '[object Array]') {
              parseAllJson(row[key])
            }
          }
        }
      }
    })(parseRows);
  }
};

export const isJSON = _isJSON;

/**
* 四舍五入保留小数位置
* @param {*} num 数值
* @param {*} digits 小数点后位数
* @param {*} isFormatQfw 是否千分位展示（即：10000.105=>10,000.105;需要的话自行开启哈）
* @returns
*/
function _getNumfixed (num, digits = 2, isFormatQfw = false) {
  let result = parseFloat(num)
  if (isNaN(result)) {
  // alert('传递参数错误，请检查！');
    return num;
  }
  result = Math.round(num * Math.pow(10, digits)) / Math.pow(10, digits);
  let s_x = result.toString();
  let pos_decimal = s_x.indexOf('.');
  if (pos_decimal < 0) {
    pos_decimal = s_x.length;
    s_x += '.';
  }
  while (s_x.length <= pos_decimal + digits) {
    s_x += '0';
  }
  return isFormatQfw ? numToQfw(s_x) : s_x;
}

/**
  *
  * @param {*} num
  * @returns
  */
function numToQfw (num) {
  // 分割小数点
  const arr = num.split('.');
  let str = arr[0];
  const reg = /(\d{1,3})(?=((\d{3}))+(\.\d*)?$)/g;
  str = str.replace(reg, '$&,');
  return `${str}.${arr[1]}`;
}

// 四舍五入保留小数位置
export const getNumfixed = _getNumfixed;

/**
 * 计算字符串长度，英文长度为1，汉字长度为2
 * @param {*} str 输入字符串
 * @returns 长度
 */
export const getStrLength = (str) => {
  // 1、定义计数器
  let len = 0, code = 0;
  // 2、遍历该字符串
  for (let i = 0; i < str.length; i++) {
    code = str.charCodeAt(i)
    if (code >= 0 && code <= 127) {
      len += 1;
    } else {
      len += 2;
    }
  }
  return len;
}

/**
 * 根据每行的长度，截取字符串
 * @param {*} str 输入字符串
 * @param {*} size 每个字符个数
 * @returns {array} 字符行数组
 */
export const subStr = (str, size) => {
  const charLength = (c) => /\S/.test(c) ? c.length === 1 ? 1 : 2 : 0; // 计算字符长度
  const lines = []; // 存储结果数组
  let currentLine = ''; // 当前行内容

  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    const charLen = charLength(char);

    if (currentLine.length + charLen <= size) { // 如果添加字符后长度不超过n
      currentLine += char;
    } else { // 否则，将当前行添加到数组并开始新行
      lines.push(currentLine);
      currentLine = char;
    }
  }

  // 添加最后一行
  if (currentLine !== '') {
    lines.push(currentLine);
  }

  return lines;
};

/**
 * 检测端口是否被占用
 * @param {string} port 端口号
 * @returns {Promise} true - 被占用 false - 未被占用
 */
export const portIsOccupied = (port) => {
  return new Promise(resolve => {
    // 创建服务并监听该端口
    const server = net.createServer().listen(port)

    server.on('listening', function () { // 执行这块代码说明端口未被占用
      server.close() // 关闭服务
      console.log('The port【' + port + '】is available.') // 控制台输出信息
      resolve(false);
    })

    server.on('error', function (err) {
      if (err.code === 'EADDRINUSE' && process.env.NODE_ENV === 'production') { // 端口已经被使用
        console.error('The port【' + port + '】is occupied, please change other port.');
      }
      resolve(true);
    })
  });
};

/**
 * 字符串转十六进制
 * @param {*} strStr 待转换字符串
 * @param {*} symbol 分隔符号
 * @returns
 */
export const stringToHex = (strStr, symbol = '') => {
  let val = '';
  const str = String(strStr);
  for (let i = 0; i < str.length; i++) {
    if (val == '') { val = str.charCodeAt(i).toString(16); } else { val += symbol + str.charCodeAt(i).toString(16); }
  }
  return val;
}

/**
 * 十六进制转换为字符串
 * @param {string} str 十六进制字符串
 * @returns {string} 字符串
 */
export const hexToString = (str) => {
  let val = '';
  const arr = str.split(',');

  for (let i = 0; i < arr.length; i++) {
    val += arr[i].fromCharCode(i);
  }

  return val;
}

/**
 * 返回请求的中的ip
 * @param {*} req 请求
 * @returns ip
 */
export const getIP = (req) => {
  const xForwardedFor = req.get('x-forwarded-for');
  let ip = (req.ip.replace(/::1/g, '') || '0.0.0.0').match(/\d+\.\d+\.\d+\.\d+/)[0];
  if (ip == '127.0.0.1' && xForwardedFor) { // 有反向代理的情况
    ip = xForwardedFor;
  }
  return ip;
}

// 建立地区树
export const regionTree = async () => {
  const regions = await sequelize.query(`SELECT id, name, parent_id
    FROM m_regions
    WHERE parent_id > 0
    ORDER BY parent_id, sort, CONVERT(name USING gbk) ASC`,
  {
    type: sequelize.QueryTypes.SELECT,
    raw: true
  });

  // 建立 ID-数组索引映射关系
  const idMapping = regions.reduce((acc, el, i) => {
    acc[el.id] = i;
    return acc;
  }, {});

  const tree = [];
  // 构造树形结构
  regions.forEach(el => {
    // 判断根节点
    if (el.parent_id === 1) {
      tree.push(el);
      return;
    }
    // 用映射表找到父元素
    const parentEl = regions[idMapping[el.parent_id]];
    // 把当前元素添加到父元素的`children`数组中
    if (parentEl) {
      try {
        parentEl.children = [...(parentEl.children || []), el];
      } catch (e) {
        console.error(parentEl, regions, idMapping, el.parent_id, e);
      }
    }
  });

  return tree;
};

/**
 * 装配查询条件
 * @param {*} filter 过滤条件
 * @param {*} searchConfig 配置
 * @param {*} raw 是否为原始类型
 * @returns 查询条件
 */
export const assemblyWhere = async ({ filter, searchConfig, raw = false, fn = null }) => {
  const where = raw ? [] : {};

  // 没有过滤条件 或 没模块，则返回
  if (!filter || !searchConfig) return where;

  // 非数组格式则返回
  if (Object.prototype.toString.call(searchConfig) !== '[object Array]') return where;

  // 配置列表转对象
  const config = searchConfig.reduce((a, b) => {
    a[b.name] = b;
    return a;
  }, {});

  // 装配查询条件
  if (raw) { // 原始类型
    for (const [key, value] of Object.entries(filter)) {
      if (value !== '' && value !== undefined && value !== null && config[key]) {
        if (config[key].op === 'between') {
          where.push(`${config[key].field} BETWEEN ${value.split('|').join(' AND ')}`);
        } else if (config[key].op === 'like') {
          where.push(`${config[key].field} LIKE ${mysql.escape('%' + value.replace(/%|'/g, '').trim() + '%')}`);
        } else if (config[key].op === 'eq') {
          if (config[key].uuid) {
            where.push(`${config[key].field} = ${mysql.escape(uuidToBin(value))}`);
          } else {
            where.push(`${config[key].field} = ${mysql.escape(String(value).trim())}`);
          }
        } else if (config[key].op === '_fn_') {
          fn && fn({ where, field: config[key].field, search: value });
        }
      }
    }
  } else { // sequelize类型
    for (const [key, value] of Object.entries(filter)) {
      if (value && config[key]) {
        if (config[key].op === 'between') {
          where[config[key].field] = { [Op.between]: value.split('|') };
        } else if (config[key].op === 'like') {
          where[config[key].field] = { [Op.like]: `%${value.replace(/%|'/g, '').trim()}%` };
        } else if (config[key].op === '_fn_') {
          fn && fn({ where, field: config[key].field, search: value });
        } else {
          if (config[key].uuid) {
            where[config[key].field] = { [Op[config[key].op]]: uuidToBin(value) };
          } else {
            where[config[key].field] = { [Op[config[key].op]]: String(value).trim() };
          }
        }
      }
    }
  }

  return where;
};

/**
 * 取数据导出模板文件的配置
 * @param {string} uid 模板的uid，可空
 * @param {string} module 模块名，可空
 * @param {string} entry 入口标识，可空
 * @returns 返回配置信息
 */
export const getDocsConfig = async ({ uid, module, entry, tid }) => {
  const config = await getSystemConfig('doc', tid);
  let docs = [];
  if (uid) {
    docs = config.filter(v => v.uid == uid && v.active == 'yes');
  }
  if (module) {
    docs = config.filter(v => v.module == module && v.active == 'yes');
  }
  if (entry) {
    docs = config.filter(v => v.entry == entry && v.active == 'yes');
  }

  docs.sort((a, b) => !a.default.localeCompare(b.default)); // 把默认的排到前面
  return docs;
};

/**
 * 拉来sequelize返回数据中的子表数据，即把object里的子表数据拉到数据主行中
 * @param {array} keys 需要拉平的对象名称，例如：['sale_detail', 'sale', ...]
 * @param {object} row 主数据行对象
 */
export const flatDetail = (keys, row) => {
  for (const item of keys) {
    if (row[item]) {
      const subValue = row[item];
      delete row[item];
      for (const [key, val] of Object.entries(subValue)) {
        row[key] = val;
      }
    }
  }
};

/**
 * 菜单默认权限
 * @param {*} type 类型 m-管理 t-租户
 * @param {*} admin 如果是管理员，强制设置用户管理权限
 */
export const defaultRights = async (type, admin = false) => {
  const defaultRights = {};

  const menu = await getSystemConfig(type === 't' ? 'menu_tenant' : 'menu_manager', 0); // 菜单信息
  // 把菜单里的隐藏项剔除
  const menuList = menu.menu.filter(v => !v.hidden && v.type === 'menu');
  // 把菜单里的隐藏权限给剔除
  for (const row of menuList) {
    defaultRights[row.name] = {};
    for (const key in row.right) {
      if (row.right[key] !== 'hidden') {
        defaultRights[row.name][key] = row.right[key];
      }
    }
  }

  if (admin) defaultRights.user.func = true; // 如果是管理员，强制设置用户管理权限
  const defaultHash = rightsHash(defaultRights);

  return {
    rights: defaultRights,
    hash: defaultHash
  }
};

/**
 * 生成权限哈希值
 * @param {string|object} rights - 权限信息，可以是字符串或对象格式。字符串格式时，应为JSON字符串。
 * @returns {string} 返回权限信息的md5哈希值。
 */
export const rightsHash = (rights) => {
  const rightObj = typeof rights == 'string' ? JSON.parse(rights) : rights;
  const rightArr = [];
  for (const menu in rightObj) {
    for (const right in rightObj[menu]) {
      rightArr.push(`${menu}:${right}:${rightObj[menu][right]}`);
    }
  }
  return md5(rightArr.sort().join(','));
}

/**
 * 查找相似物料
 * @param {array} data 待查询的物料数据
 * @param {number} standard 相似度 0~1
 * @param {number} tid 租户id
 * @returns {array} 若有相似数据，在待查询数据增加一detail属性
 */
export const findSimilarMaterial = async (data, standard, tid) => {
  // 取全部物料数据
  const allData = await models.t_materials.findAll({
    attributes: ['id', 'material_no', 'material_name', 'material_model', 'material_name_model', 'material', 'material_unit', 'brand', 'origin', 'remark'],
    where: { tenant_id: tid },
    raw: true
  });

  // 物料名称+物料规格 相似度
  const checked = JSON.parse(JSON.stringify(data));
  for (const row of checked) {
    const detail = [];
    for (const item of allData) {
      const similarity = stringSimilarity.compareTwoStrings(row.material_name + row.material_model, item.material_name + item.material_model);
      // 相似度大于标准
      if (similarity >= standard) {
        detail.push({
          ...item,
          similarity
        });
      }
    }
    detail.sort((a, b) => b.similarity - a.similarity);
    row.detail = detail;
  }
  return checked;
};

// 将二进制格式的 UUID 转换回字符串格式
export const binToUuid = (buffer, swap = true) => {
  if (!buffer) return null;
  if (buffer.length !== 16) return null;

  if (swap) { // 交换 UUID 中的时间低位部分和时间高位部分
    [buffer[6], buffer[7], buffer[4], buffer[5], buffer[0], buffer[1], buffer[2], buffer[3]] = [buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]];
  }
  const b = buffer.toString('hex').match(/.{1,2}/g);

  return `${b[0]}${b[1]}${b[2]}${b[3]}-${b[4]}${b[5]}-${b[6]}${b[7]}-${b[8]}${b[9]}-${b[10]}${b[11]}${b[12]}${b[13]}${b[14]}${b[15]}`;
}

// 将 UUID 转换为二进制格式
export const uuidToBin = (uuid, swap = true) => {
  if (!uuid) return null;
  if (uuid.replace(/-/g, '').length !== 32) return null;

  const buffer = Buffer.from(uuid.replace(/-/g, ''), 'hex');

  if (swap) { // 交换 UUID 中的时间低位部分和时间高位部分
    [buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]] = [buffer[6], buffer[7], buffer[4], buffer[5], buffer[0], buffer[1], buffer[2], buffer[3]];
  }

  return buffer;
}
